// mac osx 10.5.8
// see test1.groovy for sample code using ant copy
// contains or has logic
import java.io.*;
import java.nio.channels.*;
import java.util.zip.*;


import javax.swing.*
import java.io.*;
import java.util.*
import java.text.SimpleDateFormat

def fs = System.getProperty('file.separator')
def title =  "// ---------------------------------------------------------------"
int BUFFER = 2048;
def TEMPORARYNAME = "._harvester.tmp"

def ant = new AntBuilder()		// construct ant builder instance
def ct = 0				        // counter for number of selected resources
def rd = 0				        // counter for total number of processed resources
def pf 					        // temporary property file handle
HarvesterHelper hh			    // support module
def debugFlag = false
def tx
// ======================================
// load gui properties from prior session
pf = 'harvester.properties'
def config 
File fp = new File(pf)
if (fp.exists())
{
	config = new ConfigSlurper().parse(fp.toURL())
} // end of if to load properties
else
{
	int messageType = JOptionPane.ERROR_MESSAGE; 
	JOptionPane.showMessageDialog(null, "The $pf configuration file cannot be located", "No Configuration Error", messageType)
	System.exit(0);
} // end of else

def listpath = config.path.to.target		// vaultOutputPath
def thispath = config.path.to.source		// path to read from

thispath = (thispath.endsWith(fs)) ? thispath : thispath + fs ;	// adjusted donor path to input
listpath = (listpath.endsWith(fs)) ? listpath : listpath + fs ;	// adjusted target path to output



def includeStrategy = 1				// -1 to match from start; 0 to match anywhere; 1 to match fragment to end
def omitStrategy = 1				// -1 to match from start; 0 to match anywhere; 1 to match fragment to end
/*
	Open an audit trail text file to write names of harvested resources
*/
def listfile = listpath+"list.txt"		// vaultOutputFileList
def list = new File(listfile)			// file handle for the text file holding names of files copied to the vaultOutputPath 

def sayaudit = {fh,text -> fh.append(text+'\n'); }

// short method to print audit trail messages
def say =
{ text ->
	//if (true) println text;
	if (debugFlag) sayaudit(list,text)
}	// end of method		


def today = new Date()
def df = new SimpleDateFormat('EEE, dd MMM yyyy HH:mm:ss z')
def tt = df.format(today.time)

// load configuration settings
def searchZipsFlag = config.cb1			// include all resources found within zip compressed files
def allowDupsFlag = config.cb2			// forces serial number incrementing of resources with identical names
def removeResourceFlag = config.cb3		// after a successful capture of a resource to it's new home folder, remove original source
debugFlag = config.cb4			// cause more info to be written to list.txt audit trail thats kept with each output folder written to

// more debug text written here
say title
say "// date := ${tt}"
say "// donor path is $thispath"
say "// target path is $listpath"

// includes
def includeFlag = config.cbpa			// include all resources regardless of choice of fragments
def includeIgnoreCase = config.cbpic		// false if fragment must match case exactly, or true to convert to lowercase and match all
def includeList					// = config.includes.split(';').toList()  	
						// [""]     //".xml",".jpg",".groovy",".java",".txt",".src",".fred"]

if (config.bgis) includeStrategy = -1
if (config.bgih) includeStrategy = 0
if (config.bgos) omitStrategy = -1
if (config.bgoh) omitStrategy = 0

// set case of text for matching fragments
if (includeIgnoreCase)
{
	includeList = config.includes.toLowerCase().split(';').toList()
}
else
{
	includeList = config.includes.split(';').toList()
} // end of if


// omits
def omitFlag = config.cboa			// omit none flag
def omitIgnoreCase = config.cboic		// flag to ignore case of match; everything is lowercase unless
def omitList 					// = config.omits.split(';').toList()   	
						// [".pdf",".src",".txt"]
if (omitIgnoreCase)				// if true, convert all search fragments to lowercase
{
	omitList = config.omits.toLowerCase().split(';').toList()
}
else						// take case as declared in gui
{
	omitList = config.omits.split(';').toList()
} // end of if

// ==========================================================
// closure to write zipped inout stream to hard disk
write = {zis -> 
        def tot = 0
        def tn = listpath+TEMPORARYNAME
        try 
        {
            def tf = new File(tn)
            if (tf.exists())   tf.delete();
         
             BufferedOutputStream dest = null;
            def count = 0
            def data = new byte[BUFFER];
            // write the files to the disk
            FileOutputStream fos = new FileOutputStream(tn);
            dest = new BufferedOutputStream(fos, BUFFER);
            while ((count = zis.read(data, 0, BUFFER)) != -1) 
            {
              	dest.write(data, 0, count);
               	tot+=count
            } // end of while
        	            
            dest.flush();
            dest.close();
        } // end of try 
        catch (IOException e) 
        {
            println "zip copy:"+e.message
            throw e;
        } // end of catch
        
        return tot
} // end of write
//========================================================

// ==========================================================
// closure to perform what we want to do, now that decisions have been made
unzip = {fi, canonicalpath -> 
         BufferedOutputStream dest = null;
         FileInputStream fis = new FileInputStream(fi);
         ZipInputStream zis = new ZipInputStream(new BufferedInputStream(fis));
         ZipEntry entry;
         say "// --- zip file:"+fi.toString()
         
         // read each entry from zip file and see if it's needed'
         while((entry = zis.getNextEntry()) != null) 
		 {
		    //say entry.getName()
		    def f = new File(entry.getName())
		    def dt = entry.getTime()
		    f.setLastModified(dt)
		    
            //println "harvest      : ${f.toString()}=${f.lastModified()} sec.; entry.getTime()=${entry.getTime()} and dt=${dt}"
		    	    
		    ++rd
		    
            // does this resource name contain the partial text fragment we want ?
		    if (( includeFlag | hasAllowableFragment(f.name.toString()) ) && 
		        ( omitFlag | !hasOmitableFragment(f.name.toString()) ))
		    {
		        say "\n//... selected "+entry.getName()+" and size="+entry.getSize()+" compressed= " +    entry.getCompressedSize()

                // write zip entry to listpath+TEMPORARYNAME = /media/DURACELL/tmp/._harvester.tmp
                written = write(zis)	                        
                say "// ... writing filesize of "+written                        

	        	i = entry.getName().lastIndexOf(fs) + 1
            	def name = entry.getName().substring(i)
            	def op = listpath+name
            	say "// op starts with = $op"
                //?? harvest helper not correctly find version of zip entry even if date and size are =
            	op = hh.getName(f, op, written, entry.getTime() )
    	        say "// hh.getName() returned a name of "+op
            	say "// allowDupsFlag=$allowDupsFlag  and hh.isDup()=${hh.isDup()}"
                if ( (allowDupsFlag & hh.isDup()) | (!hh.isDup()) )
                {
                    try 
                    {
                		say "// ... renamed to $op"
	    	            Date lastModified = new Date(entry.getTime())
	    	            ct++				// bump processed count

	    	            def metadata = "['key':'$name', 'size':'${written}', 'date':'${lastModified}', 'from':'${canonicalpath.replaceAll("\'","_")}(${entry.getName()})', 'to':'${op}']";
	    	            list.append(metadata+'\n')

                        File tmp = new File(listpath+TEMPORARYNAME)
		        		File ouf = new File(op);
		        		tmp.renameTo(ouf)
		             	boolean blnSuccess = ouf.setLastModified(entry.getTime());
                    } // end of try 
                    
                    catch (IOException e) 
                    {
                        println "zip copy:"+e.message
                        throw e;
                    } // end of catch
                    
	        	} // end of if
	        } // end of if
		 } // end of while
    return true
} // end of closure

// ==========================================================
// closure to perform what we want to do, now that decisions have been made
// file handle, full resource name, resource canonical path name 

doit = {f, fn, cp ->    
	say "// doit -> '${cp}' "
	i = fn.lastIndexOf(fs) + 1
	def name = fn.substring(i)

	def of = listpath+name
    say "// of starts with = $of"
    of = hh.getName(f, of, f.size(), f.lastModified() )
    say "// hh.getName() returned a name of "+of
	say "// allowDupsFlag=$allowDupsFlag  and hh.isDup()=${hh.isDup()}"

	if ( (allowDupsFlag & hh.isDup()) | (!hh.isDup()) )
        try 
        {
    		say "// ... copying $of"
	    	File f2 = new File(of)
        	hh.localcopy(f,f2)
	    	Date lastModified = new Date(f.lastModified())
	    	ct++				// bump processed count
	    	def metadata = "['key':'$name', 'size':'${f.size()}', 'date':'${lastModified}', 'from':'${cp.replaceAll("\'","_")}', 'to':'${of}']";
	    	list.append(metadata+'\n')
    
	    	if (removeResourceFlag)
	    	{
	    		say "// removing ${cp}"
	    		f.delete()
	    	} // end of if
        } 
        catch (IOException e) 
        {
            say "// localcopy:"+e.message
            throw e;
        }

	return true;

} // end of closure


// =======================================================
// closure to see if resource name is in list of allowable fragments
hasAllowableFragment = { name ->

	def flag = false
	def fn = (includeIgnoreCase) ? name.toLowerCase() : name;

	includeList.each() {e ->
		String s = (String)e
		if ( s.length()>0) 
		{
			switch(includeStrategy)
			{
				case -1 :	if (fn.startsWith(s)) {flag=true;}  // say "includeStrategy=$includeStrategy fn=$fn s=$s";}
				case  0 :	if (fn.indexOf(s) > 0) {flag=true;} // say "includeStrategy=$includeStrategy fn=$fn s=$s";}
				case  1 :	if (fn.endsWith(s)) {flag=true;}    // say "includeStrategy=$includeStrategy fn=$fn s=$s";}
			}
		} // end of if
	} // end of each
	
	return flag
}  // end of closure



// =======================================================
// closure to see if resource name is a compressed zip file
isZip = { name ->
	return (searchZipsFlag & name.toLowerCase().endsWith(".zip"))
}  // end of closure


// =======================================================
// closure to see if resource name is in list of fragments to be ignored
hasOmitableFragment = { name ->

	def flag = false
	def fn = (omitIgnoreCase) ? name.toLowerCase() : name;

	omitList.each() {e ->
		String s = (String)e
		if ( s.length()>0) 
		{
			switch(omitStrategy)
			{
				case -1 : if (fn.startsWith(s)) {flag=true;}  // say "omitStrategy=$omitStrategy fn=$fn s=$s so ignored";}
				case  0 : if (fn.indexOf(s) > 0) {flag=true;}  // say "omitStrategy=$omitStrategy fn=$fn s=$s so ignored";}
				case  1 : if (fn.endsWith(s)) {flag=true;}  // say "omitStrategy=$omitStrategy fn=$fn s=$s so ignored";}
			}
		} // end of if
	} // end of each
	
	return flag
}  // end of closure



// ===================================
// execution code starts here :
// ===================
// start of code

hh = new HarvesterHelper()

say title
say "source := <$thispath>"
say "target := <$listpath>"
say "include:= Flag is set to <$includeFlag> allowing these items:<$includeList> with case of <$includeIgnoreCase>"
say "omit   := Flag is set to <$omitFlag> omitting these items:<$omitList> with case of <$omitIgnoreCase>"

// now lets iterate over fileset
ct = 0
boolean ok = true

def inputpath = new File(thispath)
if (inputpath.isDirectory())
{
  inputpath.eachFileRecurse  
  { f ->
    def fpn = f.toString()          // full path name
    def na = f.name.toString()      // name fragment of full file name
    def cp = f.canonicalPath        // full canonicalPath 
    
	++rd
	if (!fpn.startsWith(listpath))  // dont read the folder we're keeping copied resources into
	{
		// does this resource name contain the partial text fragment we want ?
		if (( includeFlag | hasAllowableFragment(na) | isZip(na) ) && 
		    ( omitFlag | !hasOmitableFragment(na) ))
		{
		    ok = ( isZip(na) ) ? unzip(fpn,cp) :  doit(f,fpn,cp)
		} // end of if
	} // end of else
  } // end of for scanner
} // end of directory
else
{
    if (inputpath.exists() & !inputpath.isDirectory())
    {
        tx = "// Single resource $inputpath exists and is not a directory"
        say tx
        ++rd
	    if (!inputpath.toString().startsWith(listpath))  // dont read the folder we're keeping copied resources into
	    {
	        	def i = inputpath.toString().lastIndexOf(fs) + 1
            	def na = inputpath.toString().substring(i)
            	def cp = inputpath.toString()
            	def fpn = inputpath.toString()
		    // does this resource name contain the partial text fragment we want ?
	    	if (( includeFlag | hasAllowableFragment(na) | isZip(na) ) && 
		        ( omitFlag | !hasOmitableFragment(na) ))
		    {
		        ok = ( isZip(na) ) ? unzip(fpn,cp) :  doit(inputpath,fpn,cp)
		    } // end of if
	    } // end of if
    }
    else
    {
        // bad news
        tx = "// Single resource $inputpath does not exist"
        say tx
        return tx
    } // end of file else
} // end of do a single resource


// pump out stats
def ctx = (ct==0) ? "no": ct
def rtx = (rd==0) ? "no": rd
tx = "// Found $ctx matching resources out of $rtx total resources to copy"
say tx
say title
say " "
println tx
//return tx
/*		// spare code
		//  sleep(300)
  		//  ( new AntBuilder ( ) ).copy ( file : f , tofile : ou )

*/
